const express = require('express')
const cors = require('cors')
const bodyParser = require('body-parser')
const fse = require('fs-extra')
const multer = require('multer')
const path = require('path')
const multipart = require('connect-multiparty')
const multipartMiddleware = multipart()

const app = express()

app.use(cors())
app.use(bodyParser.json())

// 所有上传的文件存放在该目录下
const UPLOADS_DIR = path.resolve('uploads')

/**
 * 上传
 */
app.post('/upload', multipartMiddleware, (req, res) => {
	const {fileHash, chunkHash} = req.body
	
	// 如果临时文件夹(用于保存分片)不存在，则创建
	const chunkDir = path.resolve(UPLOADS_DIR, fileHash)
	if (!fse.existsSync(chunkDir)) {
		fse.mkdirSync(chunkDir)
	}
	
	// 如果临时文件夹里不存在该分片，则将用户上传的分片移到临时文件夹里
	const chunkPath = path.resolve(chunkDir, chunkHash)
	if (!fse.existsSync(chunkPath)) {
		fse.moveSync(req.files.chunk.path, chunkPath)
	}
	
	res.send({
		code: 200, msg: '上传成功', fileHash
	})
})

/**
 * 合并
 */
app.post('/merge', async (req, res) => {
	const {fileHash, fileName} = req.body
	
	// 最终合并的文件路径
	const filePath = path.resolve(UPLOADS_DIR, fileHash + path.extname(fileName))
	// 临时文件夹路径
	const chunkDir = path.resolve(UPLOADS_DIR, fileHash)
	
	// 读取临时文件夹，获取该文件夹下“所有文件（分片）名称”的数组对象
	const chunkPaths = fse.readdirSync(chunkDir)
	
	// 读取临时文件夹获得的文件（分片）名称数组可能乱序，需要重新排序
	chunkPaths.sort((a, b) => a.split('-')[1] - b.split('-')[1])
	
	// 遍历文件（分片）数组，将分片追加到文件中
	const pool = chunkPaths.map((chunkName) => new Promise((resolve) => {
		const chunkPath = path.resolve(chunkDir, chunkName)
		// 将分片追加到文件中
		fse.appendFileSync(filePath, fse.readFileSync(chunkPath))
		// 删除分片
		fse.unlinkSync(chunkPath)
		resolve()
	}))
	await Promise.all(pool)
	// 等待所有分片追加到文件后，删除临时文件夹
	fse.removeSync(chunkDir)
	
	res.send({
		code: 200,
		msg: '合并成功',
		data: {fileName, filePath: '/uploads/' + fileHash + path.extname(fileName)}
	})
})

/**
 * 校验
 */
app.post('/verify', (req, res) => {
	const {fileHash, fileName} = req.body
	
	// 判断服务器上是否存在该hash值的文件
	const filePath = path.resolve(UPLOADS_DIR, fileHash + path.extname(fileName))
	const existFile = fse.existsSync(filePath)
	
	// 获取已经上传到服务器的文件分片
	const chunkDir = path.resolve(UPLOADS_DIR, fileHash)
	const existChunks = []
	if (fse.existsSync(chunkDir)) {
		existChunks.push(...fse.readdirSync(chunkDir))
	}
	
	res.send({
		code: 200,
		msg: '校验文件',
		data: {existFile, existChunks}
	})
})

const storage = multer.diskStorage({
	destination: function (req, file, cb) {
		cb(null, UPLOADS_DIR)
	}, filename: function (req, file, cb) {
		// 时间戳-6位随机字符.文件后缀
		const timeStamp = Date.now()
		const ramdomStr = Math.random().toString(36).slice(-6)
		const ext = path.extname(file.originalname)
		const filename = `${timeStamp}-${ramdomStr}${ext}`
		cb(null, filename)
	}
})

const upload = multer({
	storage, fileFilter(req, file, cb) {
		//验证文件后缀名
		const extname = path.extname(file.originalname)
		const whitelist = ['.jpg', '.gif', 'png', '.zip', '.html']
		if (whitelist.includes(extname)) {
			cb(null, true)
		} else {
			cb(new Error(`your ext name of ${extname} is not support`))
		}
	}
})

app.post('/api/upload', upload.single('file'), (req, res) => {
	const url = `/upload/${req.file.filename}`
	res.send({
		code: 200, msg: '成功', data: url
	})
})

const server = app.listen(3000, () => {
	console.log(`Example app listening on port ${server.address().port}`)
})
